int与Integer的基本使用对比

  • Integer是int的包装类;int是基本数据类型
  • Integer变量必须实例化后才能使用;int变量不需要
  • Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值
  • Integer的默认值是null;int的默认值是0

int与Integer的深入对比

  • 🤔 由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)
1
2
3
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false
  • 🤔 Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,Java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
1
2
3
Integer i = new Integer(100);
int j = 100
System.out.print(i == j); //true
  • 🤔 非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是Java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
1
2
3
Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false
  • 🤔 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间 [-128,127] 之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
1
2
3
4
5
6
7
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
  出现这个的原因:Java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100)。而Java API中对Integer类型的valueOf的定义如下,对于 [-128,127] 之间的数,会进行缓存,Integer i = 100时,会将100进行缓存,下次再写Integer j = 1100时,就会直接从缓存中取,就不会new了;如果是没有在这个区域之间的话,就会重新new一个Integer对象,导致比较时,两者的内存地址不同,所以为false
1
2
3
4
5
6
7
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}

Integer源码解析

给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,源码如下:

1
2
3
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
1
2
3
4
5
6
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

Integer a = 200;
Interger b = 200;

a 与 b相比较一般人肯定会说结果为false,因为Integer 的缓存范围是-128至127. 但其实127这个数字是可以任意改变的(当然改变的值也不能小于127)
-Djava.lang.Integer.IntegerCache.high=250
当改成250的时候,结果是true了。

IntegerCache是Integer的内部类,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 缓存支持自动装箱的对象标识语义
* -128和127(含)。
*
* 缓存在第一次使用时初始化。 缓存的大小
* 可以由-XX:AutoBoxCacheMax = <size>选项控制。
* 在VM初始化期间,java.lang.Integer.IntegerCache.high属性
* 可以设置并保存在私有系统属性中
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}

private IntegerCache() {}
}

扩展——不可变类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;

/**
* Constructs a newly allocated {@code Integer} object that
* represents the specified {@code int} value.
*
* @param value the value to be represented by the
* {@code Integer} object.
*/
public Integer(int value) {
this.value = value;
}

通过这段源码可知:

  • Integer是不可变对象,因为里面的value是final的private final int value;
  不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。
  Java提供的8个包装类(Byte、Short、Integer、Long,Double、Float、Charater、Boolean)和java.lang.String类都是不可变类,当创建它们的实例后,其实例的实例变量不可改变。
  创建自定义的不可变类,需满足以下规则:
  🎨 1.使用private和final修饰符来修饰该类的成员变量
  🎨 2.提供带参数的构造器,用于根据传入参数初始化类里的成员变量
  🎨 3.仅为类的成员变量提供getter方法,不提供setter方法
  🎨 4.如果有必要,重写Object类的equals()方法和hashCode()方法。equals()方法根据关键成员变量来作为两个对象是否相等的标准,除此之外,还应该保证两个用equals()方法判断为相等的对象的hashCode()也相等。
下面定义一个不可变的Address类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.company;

public class Address {
private final String detail;
private final String postCode;

public Address()
{
this.postCode = "";
this.detail = "";
}

public Address(String detail,String postCode)
{
this.detail = detail;
this.postCode = postCode;
}

public String getDetail()
{
return this.detail;
}

public String getPostCode()
{
return this.postCode;
}

@Override
public int hashCode() {
return detail.hashCode() + postCode.hashCode()*31;
}

@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(null != obj && obj.getClass() == Address.class)
{
Address ad = (Address)obj;
if(this.getDetail().equals(ad.getDetail())
&& this.getPostCode().equals(ad.getPostCode()))
return true;
}
return false;
}
}
  如果需要设计一个不可变类,尤其要注意其引用类型的成员变量。如果引用类型的成员变量的类是可变的,就必须采取必要的措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类。
  下面程序试图创建一个不可变的Person类,但因为Person类的一个成员变量是可变类,所以导致Person类也是个可变类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.company2;

class Name
{
private String firstName;
private String lastName;

public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Name(){}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}

public class Person {

private final Name name;

public Person(Name name)
{
//区别
this.name = name;
}

public Name getName() {
return name;
}

public static void main(String[] args)
{
Name name = new Name("悟空","孙");
Person person = new Person(name);

System.out.println(person.getName().getFirstName());

name.setFirstName("八戒");

System.out.println(person.getName().getFirstName());
}
}
  为了保持Person对象的不可变性,必须保护好Person对象的成员变量:name,让程序无法访问到Person对象的name
成员变量,也无法利用name成员变量的可变性来改变Person对象了。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {

private final Name name;
public Person(Name name)
{
//区别
this.name = new Name(name.getFirstName(),name.getLastName());
}

public Name getName() {
return name;
}
}

扩展——不同位数的操作系统数据类型所占字节

数据类型 64位机器 32位机器
char 1 1
unsigned char 1 1
signed char 1 1
int 4 4
short 2 2
long 8 4
long int 8 4
signed int 4 4
unsigned int 4 4
unsigned long int 8 4
long long 8 8
unsigned long long 8 8
float 4 4
double 8 8
long double 16 8
任何数据类型的指针 8 4

扩展——装箱与拆箱的含义

  Java中的自动装箱指的是把基本类型的值转换为对应的包装类对象,自动拆箱则相反。
  什么时候会拆箱?——基本数据类型和引用数据类型做运算时
  什么时候会装箱?——基本数据类型赋值给引用数据类型的时候
1
2
Integer i1 = 100; //装箱,把基本数据类型100包装一下放进i1
int i2 = i1; //拆箱,把基本数据类型100从i1的包装拆开赋给i2

分析如下:
(1)Integer i1 = 100实现了自动装箱,底层调用了调用包装类的valueOf方法,也就是Integer.valueOf(100)。
(2)int i2 = i1实现了自动拆箱,底层调用包装类的intValue方法。
需要注意的是:自动装箱拆箱是编译器帮我们自动转换的,我们不需要手工调用valueOf()和intValue()方法。

扩展——线程安全

  线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。(Vector,HashTab;le)
  线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。(ArrayList,LinkedList,HashMap等)
  如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题都是由全局变量及静态变量引起的。
  若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
  Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 – 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 – 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围操作(或者一系列操作)。下面几节分别描述了线程安全性的这五种类别。

👑 不可变

  不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。
  需要注意的是,对于Integer,该类不提供add方法,加法是使用+来直接操作。而 + 操作是不具线程安全的。这是提供原子操作类AtomicInteger的原。

👑 线程安全

  线程安全的对象具有在上面“线程安全”一节中描述的属性 – 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排线程都不需要任何额外的同步。这种线程安全性保证是很严格的 – 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。

👑 有条件的线程安全

  有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 – 由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 – 并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。
  如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。

👑 线程兼容

  线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。
  许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet。

👑 线程对立

  线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。

总结

  • 🌟 无论如何,Integer和new Integer不会相等。不会经历拆箱过程,前者指向常量池,后者的引用指向堆。他们的内存地址不一样。
  • 🌟 两个都是非new出来的Integer,如果数在 [-128,127] 之间(区间上限是可以调整的),则是true,否则为false。
  • 🌟 两个都是new出来的,为false。
  • 🌟 int和Integer(无论new 否),都为true,因为会把Integer自动拆箱为int再比较。
  • 🌟 Integer的缓存上限是可以调整的(当然改变的值也不能小于127)。
  • 🌟 Integer是不可变对象,因为里面的value是final的private final int value;

ps:因作者能力有限,有错误的地方请见谅

  • 喜欢这篇文章的话可以用快捷键 Ctrl + D 来收藏本页
× 请我吃糖~
打赏二维码